﻿using System;
using Microsoft.Win32;
using System.Net;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ScarecrowLibrary;
using System.Net.NetworkInformation;

namespace ScarecrowLibrary.Network
{
    /// <summary>
    /// Permette di interagire con un adattatore di rete. [WINDOWS ONLY]
    /// </summary>
    public class scNetworkAdapter
    {
        #region Static Methods
        static string Execute(string command)
        {
            string output = null;
            using (System.Diagnostics.Process prc = new System.Diagnostics.Process())
            {
                try
                {
                    prc.StartInfo = new System.Diagnostics.ProcessStartInfo("cmd.exe", "/C " + command);
                    prc.StartInfo.CreateNoWindow = true;
                    prc.StartInfo.UseShellExecute = false;
                    prc.StartInfo.RedirectStandardOutput = true;
                    prc.Start();
                    output = prc.StandardOutput.ReadToEnd();
                    prc.WaitForExit();
                }
                catch
                {
                    throw;
                }
            }
            return output;
        }
        
        /// <summary>
        /// Restituisce tutte le interfacce di rete collegate al computer.
        /// </summary>
        /// <returns>Adattatori di rete presenti nel computer.</returns>
        public static scNetworkAdapter[] GetAllInterfaces()
        {
            try
            {
                string output = Execute("netsh interface show interface");
                List<scNetworkAdapter> interfaces = new List<scNetworkAdapter>();

                byte[] byteArray = Encoding.ASCII.GetBytes(output);
                using (MemoryStream ms = new MemoryStream(byteArray))
                {
                    using (StreamReader sr = new StreamReader(ms))
                    {
                        //Le prime righe si saltano
                        sr.ReadLine();
                        sr.ReadLine();
                        sr.ReadLine();

                        //Adesso per ogni linea ho una interfaccia
                        while (!sr.EndOfStream)
                        {
                            //Leggo una linea
                            string line = sr.ReadLine();
                            if (string.IsNullOrEmpty(line))
                                continue;

                            //Ricavo il nome della connessione
                            string[] objs = line.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
                            string nic = "";
                            for (int i = 3; i < objs.Length; i++)
                            {
                                nic += objs[i] + " ";
                            }
                            nic = nic.Trim();
                            // TODO : Experimental discard of MS Virtual WIFI
                            if (nic.ToLower() == "Microsoft Virtual WiFi Miniport Adapter".ToLower())
                                continue;
                            scNetworkAdapter na = new scNetworkAdapter(nic);
                            interfaces.Add(na);
                        }
                    }
                }
                return interfaces.ToArray();
            }
            catch
            {
                throw;
            }
        }
        #endregion

        #region Properties
        /// <summary>
        /// Ottiene un valore che indica se la scheda è attiva o no.
        /// </summary>
        public bool IsEnabled
        {
            get 
            {
                NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces().Where((nic) => nic.Id.GetHashCode() == this.cachedAdapterID.GetHashCode()).ToArray();
                return nics.Length > 0;
            }
        }
        /// <summary>
        /// Ottiene l'indirizzo IP della scheda rappresentata da questa istanza.
        /// </summary>
        public string IP
        {
            get
            {
                RegistryKey ipKey = key.OpenSubKey(this.cachedAdapterID);
                string ip = "";
                string[] vect = (string[])ipKey.GetValue("IPAddress");
                if (vect != null && vect.Length > 0)
                {
                    ip = vect[0];
                }
                else
                {
                    ip = (string)ipKey.GetValue("DhcpIPAddress");
                }
                return ip ?? "";
            }
        }
        /// <summary>
        /// Ottiene la Subnet Mask della scheda rappresentata da questa istanza.
        /// </summary>
        public string Subnet
        {
            get
            {
                RegistryKey subnetKey = key.OpenSubKey(this.cachedAdapterID);
                string subnet = "";
                string[] vect = (string[])subnetKey.GetValue("SubnetMask");
                if (vect != null && vect.Length > 0)
                {
                    subnet = vect[0];
                }
                else
                {
                    subnet = (string)subnetKey.GetValue("DhcpSubnetMask");
                }
                return subnet ?? "";
            }
        }
        /// <summary>
        /// Ottiene il Gateway della scheda rappresentata da questa istanza.
        /// </summary>
        public string Gateway
        {
            get
            {
                RegistryKey subnetKey = key.OpenSubKey(this.cachedAdapterID);
                string subnet = "";
                string[] vect = (string[])subnetKey.GetValue("DefaultGateway");
                if (vect != null && vect.Length > 0)
                {
                    subnet = vect[0];
                }
                else
                {
                    vect = (string[])subnetKey.GetValue("DhcpDefaultGateway");
                    if (vect != null && vect.Length > 0)
                    {
                        subnet = vect[0];
                    }
                }
                return subnet ?? "";
            }
        }
        public string[] Dnss
        {
            get
            {
                RegistryKey dnsKey = key.OpenSubKey(this.cachedAdapterID);
                string dns = (string)dnsKey.GetValue("NameServer");
                if (string.IsNullOrWhiteSpace(dns))
                    dns = (string)dnsKey.GetValue("DhcpNameServer");
                dns = dns ?? ",";
                string[] dnss = dns.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                return dnss;
            }
        }
        /// <summary>
        /// Ottiene il DNS Primario della scheda rappresentata da questa istanza.
        /// </summary>
        public string DNS1
        {
            get
            {
                string[] dnss = Dnss;
                return (dnss.Length > 0) ? dnss[0] : "";
            }
        }
        /// <summary>
        /// Ottiene il DNS Secondario della scheda rappresentata da questa istanza.
        /// </summary>
        public string DNS2
        {
            get
            {
                string[] dnss = Dnss;
                return (dnss.Length > 1) ? dnss[1] : "";
            }
        }

        public bool IsIPDhcp
        {
            get
            {
                RegistryKey ipDhcpKey = key.OpenSubKey(this.cachedAdapterID);
                object o = ipDhcpKey.GetValue("EnableDHCP");
                return Convert.ToBoolean(o);
            }
        }
        public bool IsDNSDhcp
        {
            get
            {
                RegistryKey dnsDhcp = key.OpenSubKey(this.cachedAdapterID);
                return IsIPDhcp && string.IsNullOrWhiteSpace(Convert.ToString(dnsDhcp.GetValue("NameServer")));
            }
        }
        /// <summary>
        /// Ottiene il nome della scheda rappresentata da questa istanza.
        /// </summary>
        public string ConnectionName
        {
            get;
            private set;
        }
        #endregion

        #region Fields
        NetworkInterface managedNic;
        string cachedAdapterID = null;
        RegistryKey key = Registry.LocalMachine.OpenSubKey(@"System\CurrentControlSet\Services\Tcpip\Parameters\Interfaces");
        #endregion

        #region InternalMethods
        
        #endregion

        #region Methods
        /// <summary>
        /// Inizializza una scheda di rete.
        /// </summary>
        /// <param name="ConnectionName">Nome della scheda.</param>
        public scNetworkAdapter(string ConnectionName)
        {
            this.ConnectionName = ConnectionName;
            NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces().Where((nic) => nic.Name.GetHashCode() == this.ConnectionName.GetHashCode()).ToArray();
            if (nics.Length <= 0)
            {
                Enable();
                nics = NetworkInterface.GetAllNetworkInterfaces().Where((nic) => nic.Name.GetHashCode() == this.ConnectionName.GetHashCode()).ToArray();
                Disable();
            }
            this.managedNic = nics[0];
            this.cachedAdapterID = this.managedNic.Id;
        }
        /// <summary>
        /// Imposto l'indirizzo IP statico. È necessario impostare anche i Server DNS.
        /// </summary>
        /// <param name="ip">Indirizzo IP.</param>
        /// <param name="subnetmask">SubnetMask.</param>
        /// <param name="gateway">Gateway.</param>
        public void SetIPAddress(string ip, string subnetmask, string gateway)
        {
            if (string.IsNullOrWhiteSpace(ip) || string.IsNullOrWhiteSpace(subnetmask))
                throw new Exception("Argomento nullo!");
            if (string.IsNullOrWhiteSpace(gateway))
                gateway = "";
            string command = string.Format("netsh interface ip set address \"{0}\" static {1} {2} {3}", this.ConnectionName, ip, subnetmask, gateway);
            Execute(command);
        }
        /// <summary>
        /// Imposto l'indirizzo IP dinamico.
        /// </summary>
        public void SetIPDHCP()
        {
            string command = string.Format("netsh interface ip set address \"{0}\" dhcp", this.ConnectionName);
            Execute(command);
        }
        /// <summary>
        /// Imposto i DNS.
        /// </summary>
        /// <param name="dns1">DNS Primario.</param>
        /// <param name="dns2">DNS Secondario.</param>
        public void SetDNS(string dns1, string dns2)
        {
            //Pulisco la lista dei DNS
            string command = string.Format("netsh interface ip delete dns \"{0}\" all", this.ConnectionName);
            Execute(command);

            if (!string.IsNullOrWhiteSpace(dns1))
            {
                //Imposto il DNS primario
                command = string.Format("netsh interface ip set dns \"{0}\" static {1}", this.ConnectionName, dns1);
                Execute(command);
            }
            //Imposto il DNS secondario
            if (!string.IsNullOrWhiteSpace(dns2))
            {
                command = string.Format("netsh interface ip add dns \"{0}\" {1}", this.ConnectionName, dns2);
                Execute(command);
            }
        }
        /// <summary>
        /// Imposta l'indirizzi DNS Dinamici.
        /// </summary>
        public void SetDNSDHCP()
        {
            string command = string.Format("netsh interface ip set dns \"{0}\" dhcp", this.ConnectionName);
            Execute(command);
        }

        /// <summary>
        /// Abilita la scheda di rete.
        /// </summary>
        public void Enable()
        {
            string command = string.Format("netsh interface set interface \"{0}\" admin=ENABLED", this.ConnectionName);
            Execute(command);
        }
        /// <summary>
        /// Disabilita la scheda di rete.
        /// </summary>
        public void Disable()
        {
            string command = string.Format("netsh interface set interface \"{0}\" admin=DISABLED", this.ConnectionName);
            Execute(command);
        }
        /// <summary>
        /// Imposta il DHCP per tutti i campi.
        /// </summary>
        public void SetDHCP()
        {
            SetIPDHCP();
            SetDNSDHCP();
        }
        /// <summary>
        /// Ripara la connessione selezionata.
        /// </summary>
        public void Repair()
        {
            System.Diagnostics.Process cloak = new System.Diagnostics.Process();
            try
            {
                //From http://support.microsoft.com/kb/289256/EN-US/
                //Renew DHCP Lease
                cloak.StartInfo = new System.Diagnostics.ProcessStartInfo("IPConfig", "/release");
                cloak.StartInfo.CreateNoWindow = true;
                cloak.StartInfo.UseShellExecute = false;
                cloak.Start();
                cloak.WaitForExit();
                cloak.StartInfo = new System.Diagnostics.ProcessStartInfo("IPConfig", "/renew");
                cloak.StartInfo.CreateNoWindow = true;
                cloak.StartInfo.UseShellExecute = false;
                cloak.Start();
                cloak.WaitForExit();

                //Flush ARP Cache
                cloak.StartInfo = new System.Diagnostics.ProcessStartInfo("arp", "-d *");
                cloak.StartInfo.CreateNoWindow = true;
                cloak.StartInfo.UseShellExecute = false;
                cloak.Start();
                cloak.WaitForExit();

                //Reload the NetBios name cache
                cloak.StartInfo = new System.Diagnostics.ProcessStartInfo("nbtstat", "R");
                cloak.StartInfo.CreateNoWindow = true;
                cloak.StartInfo.UseShellExecute = false;
                cloak.Start();
                cloak.WaitForExit();

                //NetBIOS name update is sent
                cloak.StartInfo = new System.Diagnostics.ProcessStartInfo("nbtstat", "RR");
                cloak.StartInfo.CreateNoWindow = true;
                cloak.StartInfo.UseShellExecute = false;
                cloak.Start();
                cloak.WaitForExit();

                //Domain Name System (DNS) cache is flushed
                cloak.StartInfo = new System.Diagnostics.ProcessStartInfo("ipconfig", "/flushdns");
                cloak.StartInfo.CreateNoWindow = true;
                cloak.StartInfo.UseShellExecute = false;
                cloak.Start();
                cloak.WaitForExit();

                //DNS name registration
                cloak.StartInfo = new System.Diagnostics.ProcessStartInfo("ipconfig", "/registerdns");
                cloak.StartInfo.CreateNoWindow = true;
                cloak.StartInfo.UseShellExecute = false;
                cloak.Start();
                cloak.WaitForExit();

                string command = "netsh winsock reset";
                Execute(command);
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                cloak.Dispose();
            }
        }
        #endregion

        #region Export
        public scAdapterProfile ExportToProfile()
        {
            scAdapterProfile ap = new scAdapterProfile();
            ap.Enabled = this.IsEnabled;
            ap.NetworkAdapter = this.ConnectionName;
            ap.IsIPDhcp = this.IsIPDhcp;
            ap.IPAddress = this.IP;
            ap.SubnetMask = this.Subnet;
            ap.Gateway = this.Gateway;
            ap.IsDNSDhcp = this.IsDNSDhcp;
            ap.DNS1 = this.DNS1;
            ap.DNS2 = this.DNS2;
            return ap;
        }
        #endregion

        #region Override
        public override string ToString()
        {
            return this.ConnectionName;
        }
        public override int GetHashCode()
        {
            return this.ConnectionName.GetHashCode();
        }
        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;

            scNetworkAdapter na = obj as scNetworkAdapter;
            if ((object)na == null)
                return false;

            return this.GetHashCode() == na.GetHashCode();
        }
        public bool Equals(scNetworkAdapter na)
        {
            if ((object)na == null)
                return false;

            return this.GetHashCode() == na.GetHashCode();
        }
        public static bool operator ==(scNetworkAdapter na, scNetworkAdapter nb)
        {
            if (object.ReferenceEquals(na, nb))
                return true;

            if ((object)na == null || (object)nb == null)
                return false;

            return na.GetHashCode() == nb.GetHashCode();
        }
        public static bool operator !=(scNetworkAdapter na, scNetworkAdapter nb)
        {
            return !(na == nb);
        }
        #endregion
    }
}
